package org.darkbits.cyklon.gear;

import org.darkbits.cyklon.annotation.*;
import org.darkbits.cyklon.machine.ProcessException;
import org.darkbits.cyklon.machine.AbstractReflectedMachine;

public class CrossDelay extends AbstractReflectedMachine {
	private float delay = 200.0f;
	private float feedback = 0.5f;
	private float dryWetMix = 0.5f;
	
	private static final int BUFFER_SIZE = 45000;
	transient private float inLeft[], inRight[];
	transient private float outLeft[], outRight[];
	transient private float bufferLeft[] = new float[BUFFER_SIZE];
	transient private float bufferRight[] = new float[BUFFER_SIZE]; 
	transient private int bufferPos;
	
	@Property(min = 0.0f, max = 1000.0f, order = 0)
	public void setDelay(float delay) {
		this.delay = delay;
	}
	
	public float getDelay() {
		return delay;
	}

	public String valueOfDelay() {
		return String.valueOf((int)delay) + " ms";
	}

	@Property(order = 1)
	public void setFeedback(float feedback) {
		this.feedback = feedback;
	}
	
	public float getFeedback() {
		return feedback;
	}
	
	public String valueOfFeedback() {
		return String.valueOf((int)(feedback * 100)) + "%";
	}
	
	@Property(name = "Dry/Wet Mix", order = 2)
	public void setDryWetMix(float dryWetMix) {
		this.dryWetMix = dryWetMix;
	}
	
	public float getDryWetMix() {
		return dryWetMix;
	}
	
	public String valueOfDryWetMix() {
		return String.valueOf((int)(dryWetMix * 100)) + "%";
	}

	@DefaultLeft @DefaultMono @Input
	public void setInLeft(float in[]) {
		this.inLeft = in;
	}

	@DefaultRight @Input
	public void setInRight(float in[]) {
		this.inRight = in;
	}
	
	@DefaultLeft @DefaultMono @Output
	public float[] getOutLeft() {
		return this.outLeft;
	}
	
	@DefaultLeft @Output
	public float[] getOutRight() {
		return this.outRight;
	}
	
	private float inLeftAt(int i) {
		if (inLeft == null) {
			return 0;
		} else {
			return inLeft[i];
		}
	}
	
	private float inRightAt(int i) {
		if (inRight == null) {
			return 0;
		} else {
			return inRight[i];
		}
	}
	
	private boolean isMonoInput() {
		return inRight == null;
	}
	
	private boolean isUnconnected() {
		return inLeft == null && inRight == null;
	}

	public void process(int samples) throws ProcessException {
		if (isUnconnected() && outLeft == null && outRight == null) {
			return;
		}
		
		if (outLeft == null || outLeft.length != samples) {
			outLeft = new float[samples];
		}
		
		if (outRight == null || outRight.length != samples) {
			outRight = new float[samples];
		}
		
		int offset = (int)(delay * 4.41);
		
		for (int i = 0; i < samples; i++) {
			float delayedLeft = bufferLeft[(bufferPos + i - offset + BUFFER_SIZE) % BUFFER_SIZE];
			float delayedRight = bufferRight[(bufferPos + i - offset + BUFFER_SIZE) % BUFFER_SIZE];			
						
			if (isMonoInput()) {
				float inputMono = inLeftAt(i);
				
				outLeft[i] = delayedLeft * dryWetMix + inputMono * (1.0f - dryWetMix);
				outRight[i] = delayedRight * dryWetMix + inputMono * (1.0f - dryWetMix);
				
				bufferLeft[(bufferPos + i) % BUFFER_SIZE] = delayedRight * feedback;
				bufferRight[(bufferPos + i) % BUFFER_SIZE] = inputMono + delayedLeft * feedback;
			} else {
				float inputLeft = inLeftAt(i);
				float inputRight = inRightAt(i);
				
				outLeft[i] = delayedLeft * dryWetMix + inputLeft * (1.0f - dryWetMix);
				outRight[i] = delayedRight * dryWetMix + inputRight * (1.0f - dryWetMix);
				
				bufferLeft[(bufferPos + i) % BUFFER_SIZE] = inputRight + delayedRight * feedback;
				bufferRight[(bufferPos + i) % BUFFER_SIZE] = inputLeft + delayedLeft * feedback;
			}
		}
		
		bufferPos = (bufferPos + samples) % BUFFER_SIZE;
	}
}
